package com.dengwei.smali.first.dwpull_to_refresh.PullToRefresh;

import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.Scroller;
import android.widget.TextView;

import com.dengwei.smali.first.dwpull_to_refresh.PullToRefresh.indicator.PullToRereshIndicator;
import com.dengwei.smali.first.dwpull_to_refresh.R;

/**
 * Created by dengwei on 2017/3/18.
 */

public class PullToRefreshFrameLayout extends ViewGroup {

    public final static byte STATUS_INIT = 1;                                               //初始状态
    public final static byte STATUS_PREPARE = 2;                                            //向下滑动状态
    public final static byte STATUS_LOADING = 3;                                            //刷新状态
    public final static byte STATUS_COMPLETE = 4;                                           //完成状态
    private byte mStatus = STATUS_INIT;

    private int mFlag = 0x00;
    private final static byte FLAG_AUTO_REFRESH_AT_ONCE = 1;
    private final static byte FLAG_AUTO_REFRESH_BUT_LATER = 1 << 1;
    private final static byte FLAG_ENABLE_NEXT_PTR_AT_ONCE = 1 << 2;
    private final static byte FLAG_PIN_CONTENT = 1 << 3;
    private final static byte MASK_AUTO_REFRESH = 3;

    private PullToRereshIndicator mIndicator;
    private int mHeaderId;
    private int mContentId;
    private int mDurationToClose = 200;
    private int mDurationToCloseHeader = 1000;
    private boolean mKeepHeaderWhenRefresh = true;
    private boolean mIsRefresh;
    private int mTouchSlop;
    private long mLoadingStartTime;
    private boolean mPullToRefresh = false;
    private boolean mPreventForHorizontal = false;                                          //横向移动禁止
    private boolean mDisableWhenHorizontalMove = false;
    private int mLoadingMinTime = 500;
    private Runnable mPerformRefreshCompleteDelay = new Runnable() {
        @Override
        public void run() {
            performRefreshComplete();
        }
    };

    private View mHeaderView;
    private View mContentView;

    private boolean mHasSendCancelEevent;
    private PullToRefreshHandlerHolder mUIHandlerHolder = PullToRefreshHandlerHolder.create();
    private PullToRefreshHandler mHandler;
    private ScrollChecker mScrollChecker;

    private MotionEvent mLastMoveEvent;

    public PullToRefreshFrameLayout(Context context) {
        this(context, null);
    }

    public PullToRefreshFrameLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PullToRefreshFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mIndicator = new PullToRereshIndicator();
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PullToRefresh, 0, 0);
        if (typedArray != null) {
            try {
                mHeaderId = typedArray.getResourceId(R.styleable.PullToRefresh_header, mHeaderId);
                mContentId = typedArray.getResourceId(R.styleable.PullToRefresh_content, mContentId);
                mIndicator.setResistance(typedArray.getFloat(R.styleable.PullToRefresh_resistance, mIndicator.getResistance()));
                mDurationToClose = typedArray.getInt(R.styleable.PullToRefresh_duration_to_close, mDurationToClose);
                mDurationToCloseHeader = typedArray.getInt(R.styleable.PullToRefresh_duration_to_close_header, mDurationToCloseHeader);
                mIndicator.setRatioOfHeaderHeightToRefresh(typedArray.getFloat(R.styleable.PullToRefresh_ratio_of_header_height_to_refresh, mIndicator.getRatioOfHeaderHeightToRefresh()));
                mKeepHeaderWhenRefresh = typedArray.getBoolean(R.styleable.PullToRefresh_keep_header_when_refresh, mKeepHeaderWhenRefresh);
                mIsRefresh = typedArray.getBoolean(R.styleable.PullToRefresh_pull_to_fresh, mIsRefresh);
            } finally {
                typedArray.recycle();
            }
        }
        mScrollChecker = new ScrollChecker();
        ViewConfiguration tempCof = ViewConfiguration.get(getContext());
        //// TODO: 2017/3/18 为什么要乘以2
        mTouchSlop = tempCof.getScaledTouchSlop() * 2;

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        if (mHeaderView != null) {
            measureChildWithMargins(mHeaderView, widthMeasureSpec, 0, heightMeasureSpec, 0);
            MarginLayoutParams layoutParams = (MarginLayoutParams) mHeaderView.getLayoutParams();
            int headHeight = mHeaderView.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;
            mIndicator.setHeaderHeight(headHeight);
        }

        if (mContentView != null) {
            measuContentView(mContentView, widthMeasureSpec, heightMeasureSpec);
        }
    }

    /**
     * 测量ContentView
     *
     * @param child
     * @param parentWidthMeasureSpec
     * @param parentHeightMeasureSpec
     */
    private void measuContentView(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
        MarginLayoutParams layoutParams = (MarginLayoutParams) child.getLayoutParams();
        int widthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, getPaddingLeft() + getPaddingRight() + layoutParams.leftMargin + layoutParams.rightMargin, layoutParams.width);
        int heightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, getPaddingTop() + getPaddingBottom() + layoutParams.topMargin + layoutParams.bottomMargin, layoutParams.height);
        child.measure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
        layoutChildren();
    }

    private void layoutChildren() {
        int offset = mIndicator.getCurrentPos();
        int paddingLeft = getPaddingLeft();
        int paddingTop = getPaddingTop();

        if (mHeaderView != null) {
            MarginLayoutParams layoutParams = (MarginLayoutParams) mHeaderView.getLayoutParams();
            int left = paddingLeft + layoutParams.leftMargin;
            int top = -(mIndicator.getHeaderHeight() - paddingTop - layoutParams.topMargin - offset);
            int right = left + mHeaderView.getMeasuredWidth();
            int bottom = top + mHeaderView.getMeasuredHeight();
            mHeaderView.layout(left, top, right, bottom);
        }
        if (mContentView != null) {
            if (isPinContent()) {
                offset = 0;
            }
            MarginLayoutParams layoutParams = (MarginLayoutParams) mContentView.getLayoutParams();
            int left = paddingLeft + layoutParams.leftMargin;
            int top = paddingTop + layoutParams.topMargin + offset;
            int right = left + mContentView.getMeasuredWidth();
            int bottom = top + mContentView.getMeasuredHeight();
            mContentView.layout(left, top, right, bottom);
        }

    }

    public boolean dispatchTouchEventSupper(MotionEvent e) {
        return super.dispatchTouchEvent(e);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (!isEnabled() || mContentView == null || mHeaderView == null)
            return dispatchTouchEventSupper(ev);

        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                mHasSendCancelEevent = false;
                mIndicator.onPressDown(ev.getX(), ev.getY());
                mScrollChecker.abortIfWorking();
                mPreventForHorizontal = false;
                dispatchTouchEventSupper(ev);
                return true;
            case MotionEvent.ACTION_MOVE:
                mLastMoveEvent = ev;
                mIndicator.onMove(ev.getX(), ev.getY());
                float offsetX = mIndicator.getOffsetX();
                float offsetY = mIndicator.getOffsetY();
                if (mDisableWhenHorizontalMove && !mPreventForHorizontal && (Math.abs(offsetX) > mTouchSlop && Math.abs(offsetX) > Math.abs(offsetY))) {
                    if (mIndicator.isInStartPosition()) {
                        mPreventForHorizontal = true;
                    }
                }
                if (mPreventForHorizontal) {
                    return dispatchTouchEventSupper(ev);
                }

                boolean isMoveDown = offsetY > 0;
                boolean isMoveUp = !isMoveDown;
                boolean isCanMoveUp = mIndicator.hasLeftStartPosition();
                if (isMoveDown && mHandler != null && !mHandler.checkCanDoRefresh(this, mContentView, mHeaderView)) {
                    return dispatchTouchEventSupper(ev);
                }

                if ((isMoveUp && isCanMoveUp) || isMoveDown) {
                    movePos(offsetY);
                    return true;
                }
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mIndicator.onRelease();
                if (mIndicator.hasLeftStartPosition()) {
                    onRelease(false);
                    if (mIndicator.hasMoveAfterPressdDown()) {
                        sendCancelEvent();
                        return true;
                    }
                    return dispatchTouchEventSupper(ev);
                } else {
                    return dispatchTouchEventSupper(ev);
                }
        }
        return dispatchTouchEventSupper(ev);
    }

    public void autoRefresh() {
        autoRefresh(true, mDurationToCloseHeader);
    }

    public void autoRefresh(boolean atOnce) {
        autoRefresh(atOnce, mDurationToCloseHeader);
    }

    public void autoRefresh(boolean atOnce, int duration) {

        if (mStatus != STATUS_INIT) {
            return;
        }

        mFlag |= atOnce ? FLAG_AUTO_REFRESH_AT_ONCE : FLAG_AUTO_REFRESH_BUT_LATER;

        mStatus = STATUS_PREPARE;
        if (mUIHandlerHolder.hasHandler()) {
            mUIHandlerHolder.onUIRefreshPrepare(this);
        }
        mScrollChecker.tryToScrollTo(mIndicator.getOffsetToRefresh(), duration);
        if (atOnce) {
            mStatus = STATUS_LOADING;
            performRefresh();
        }
    }

    private void clearFlag() {
        // remove auto fresh flag
        mFlag = mFlag & ~MASK_AUTO_REFRESH;
    }

    public boolean isAutoRefresh() {
        return (mFlag & MASK_AUTO_REFRESH) > 0;
    }

    private boolean performAutoRefreshButLater() {
        return (mFlag & MASK_AUTO_REFRESH) == FLAG_AUTO_REFRESH_BUT_LATER;
    }

    public boolean isEnabledNextPtrAtOnce() {
        return (mFlag & FLAG_ENABLE_NEXT_PTR_AT_ONCE) > 0;
    }

    public void setEnabledNextPtrAtOnce(boolean enable) {
        if (enable) {
            mFlag = mFlag | FLAG_ENABLE_NEXT_PTR_AT_ONCE;
        } else {
            mFlag = mFlag & ~FLAG_ENABLE_NEXT_PTR_AT_ONCE;
        }
    }

    public boolean isPinContent() {
        return (mFlag & FLAG_PIN_CONTENT) > 0;
    }

    public void setPinContent(boolean pinContent) {
        if (pinContent) {
            mFlag = mFlag | FLAG_PIN_CONTENT;
        } else {
            mFlag = mFlag & ~FLAG_PIN_CONTENT;
        }
    }

    /**
     * 如果deltaY 大于 0 ，则Content向下移动
     *
     * @param deltaY
     */
    private void movePos(float deltaY) {
        if (deltaY < 0 && mIndicator.isInStartPosition()) {
            return;
        }

        int toY = mIndicator.getCurrentPos() + (int) deltaY;
        //如果小于初始位置 回滚初始位置
        if (mIndicator.willOverTop(toY)) {
            toY = mIndicator.POS_START;
        }
        mIndicator.setCurrentPos(toY);
        int distance = toY - mIndicator.getLastPosY();
        updatePos(distance);
    }

    /**
     * 移动到指定位置
     *
     * @param distance
     */
    private void updatePos(int distance) {
        if (distance == 0) return;

        boolean isUnderTouch = mIndicator.isUnderTouch();
        if (isUnderTouch && !mHasSendCancelEevent && mIndicator.hasMoveAfterPressdDown()) {
            mHasSendCancelEevent = true;
            sendCancelEvent();
        }
        if (mIndicator.hasJustLeftStartPosition() && mStatus == STATUS_INIT || (mIndicator.goDownCrossFinishPosition() && mStatus == STATUS_COMPLETE && isEnabledNextPtrAtOnce())){
            mStatus = STATUS_PREPARE;
            mUIHandlerHolder.onUIRefreshPrepare(this);
        }

        if (mIndicator.hasJustBackToStartPosition()) {
            tryToNotifyReset();
            if (isUnderTouch) {
                sendDownEvent();
            }
        }

        if (mStatus == STATUS_PREPARE) {
            if (isUnderTouch && isAutoRefresh() && mPullToRefresh && mIndicator.crossRefreshLineFromTopToBottom()) {
                tryToPerformRefresh();
            }
            if (performAutoRefreshButLater() && mIndicator.hasJustReachedHeaderHeightFromTopToBottom()) {
                tryToPerformRefresh();
            }
        }

        mHeaderView.offsetTopAndBottom(distance);
        if (!isPinContent()) mContentView.offsetTopAndBottom(distance);
        invalidate();
        if (mUIHandlerHolder.hasHandler())
            mUIHandlerHolder.onUIPositionChange(this, isUnderTouch, mStatus, mIndicator);
        onPositionChange(isUnderTouch, mStatus, mIndicator);
    }

    protected void onPositionChange(boolean isInTouching, int status, PullToRereshIndicator mIndicator) {
    }

    private void sendCancelEvent() {
        if (mLastMoveEvent == null) {
            return;
        }
        MotionEvent last = mLastMoveEvent;
        MotionEvent e = MotionEvent.obtain(last.getDownTime(), last.getEventTime() + ViewConfiguration.getLongPressTimeout(), MotionEvent.ACTION_CANCEL, last.getX(), last.getY(), last.getMetaState());
        dispatchTouchEventSupper(e);
    }

    private void sendDownEvent() {
        final MotionEvent last = mLastMoveEvent;
        MotionEvent e = MotionEvent.obtain(last.getDownTime(), last.getEventTime(), MotionEvent.ACTION_DOWN, last.getX(), last.getY(), last.getMetaState());
        dispatchTouchEventSupper(e);
    }

    private void onRelease(boolean forLoading) {
        tryToPerformRefresh();
        if (mStatus == STATUS_LOADING) {
            if (mKeepHeaderWhenRefresh) {
                if (mIndicator.isOverOffsetToKeepHeaderWhileLoading() && !forLoading) {
                    mScrollChecker.tryToScrollTo(mIndicator.getOffsetToKeepHeaderWhileLoading(), mDurationToClose);
                }
            } else {
                tryScrollBackToWhileLoading();
            }
        } else {
            if (mStatus == STATUS_COMPLETE) {
                notifyUIRefreshComplete();
            } else {
                tryScrollBackToTopAbortRefresh();
            }
        }
    }

    public final void refreshComplete() {

        int delay = (int) (mLoadingMinTime - (System.currentTimeMillis() - mLoadingStartTime));
        if (delay <= 0) {
            performRefreshComplete();
        } else {
            postDelayed(mPerformRefreshCompleteDelay, delay);
        }
    }

    private void performRefreshComplete() {
        mStatus = STATUS_COMPLETE;
        if (mScrollChecker.mIsRunning && isAutoRefresh()) {
            return;
        }
        notifyUIRefreshComplete();
    }

    protected void onScrollAbort() {
        if (mIndicator.hasLeftStartPosition() && isAutoRefresh()) {
            onRelease(true);
        }
    }

    protected void onScrollFinish() {
        if (mIndicator.hasLeftStartPosition() && isAutoRefresh()) {
            onRelease(true);
        }
    }

    private void tryScrollBackToWhileLoading() {
        tryScrollBackToTop();
    }

    public void addPtrUIHandler(PullToRefreshUIHandler uIHandler) {
        mUIHandlerHolder.addHandler(mUIHandlerHolder, uIHandler);
    }

    /**
     * 是否是刷新状态
     *
     * @return
     */
    public boolean isRefreshing() {
        return mStatus == STATUS_LOADING;
    }

    /**
     * 执行回退
     */
    private void tryScrollBackToTop() {
        if (!mIndicator.isUnderTouch()) {
            mScrollChecker.tryToScrollTo(mIndicator.POS_START, mDurationToCloseHeader);
        }
    }

    /**
     * 执行刷新动作
     * 如果不为下拉状态则直接返回
     * @return
     */
    private boolean tryToPerformRefresh() {
        if (mStatus != STATUS_PREPARE) {
            return false;
        }
        if ((mIndicator.isOverOffsetToKeepHeaderWhileLoading() && isAutoRefresh()) || mIndicator.isOverOffsetToRefresh()) {
            mStatus = STATUS_LOADING;
            performRefresh();
        }
        return false;
    }

    private void tryScrollBackToTopAfterComplete() {
        tryScrollBackToTop();
    }

    private void tryScrollBackToTopAbortRefresh() {
        tryScrollBackToTop();
    }

    /**
     * 执行刷新
     */
    private void performRefresh() {
        mLoadingStartTime = System.currentTimeMillis();
        if (mUIHandlerHolder.hasHandler()) {
            mUIHandlerHolder.onUIRefreshBegin(this);
        }
        if (mHandler != null) {
            mHandler.onRefreshBegin(this);
        }
    }

    public boolean isPullToRefresh() {
        return mPullToRefresh;
    }

    public int getOffsetToRefresh() {
        return mIndicator.getOffsetToRefresh();
    }

    /**
     * 执行刷新完成
     *
     */
    private void notifyUIRefreshComplete() {
        if (mUIHandlerHolder.hasHandler()) {
            mUIHandlerHolder.onUIRefreshComplete(this);
        }
        mIndicator.onUIRefreshComplete();
        tryScrollBackToTopAfterComplete();
        tryToNotifyReset();
    }

    /**
     * 重置刷新
     *
     * @return
     */
    private boolean tryToNotifyReset() {
        if ((mStatus == STATUS_COMPLETE || mStatus == STATUS_PREPARE) && mIndicator.isInStartPosition()) {
            if (mUIHandlerHolder.hasHandler()) {
                mUIHandlerHolder.onUIReset(this);
            }
            mStatus = STATUS_INIT;
            clearFlag();
            return true;
        }
        return false;
    }

    public View getHeaderView() {
        return mHeaderView;
    }

    public void setHeaderView(View headerView) {
        if (mHeaderView != null && headerView != null && mHeaderView != headerView) {
            removeView(mHeaderView);
        }
        ViewGroup.LayoutParams layoutParams = headerView.getLayoutParams();
        if (layoutParams == null) {
            layoutParams = new LayoutParams(MarginLayoutParams.MATCH_PARENT, MarginLayoutParams.WRAP_CONTENT);
            headerView.setLayoutParams(layoutParams);
        }
        mHeaderView = headerView;
        addView(mHeaderView);
    }

    @Override
    protected void onFinishInflate() {
        final int childCount = getChildCount();
        if (childCount > 2) {
            throw new IllegalStateException("PtrFrameLayout can only contains 2 children");
        } else if (childCount == 2) {
            if (mHeaderId != 0 && mHeaderView == null) {
                mHeaderView = findViewById(mHeaderId);
            }
            if (mContentId != 0 && mContentView == null) {
                mContentView = findViewById(mContentId);
            }

            // not specify header or content
            if (mContentView == null || mHeaderView == null) {

                View child1 = getChildAt(0);
                View child2 = getChildAt(1);
                if (child1 instanceof PullToRefreshUIHandler) {
                    mHeaderView = child1;
                    mContentView = child2;
                } else if (child2 instanceof PullToRefreshUIHandler) {
                    mHeaderView = child2;
                    mContentView = child1;
                } else {
                    // both are not specified
                    if (mContentView == null && mHeaderView == null) {
                        mHeaderView = child1;
                        mContentView = child2;
                    }
                    // only one is specified
                    else {
                        if (mHeaderView == null) {
                            mHeaderView = mContentView == child1 ? child2 : child1;
                        } else {
                            mContentView = mHeaderView == child1 ? child2 : child1;
                        }
                    }
                }
            }
        } else if (childCount == 1) {
            mContentView = getChildAt(0);
        } else {
            TextView errorView = new TextView(getContext());
            errorView.setClickable(true);
            errorView.setTextColor(0xffff6600);
            errorView.setGravity(Gravity.CENTER);
            errorView.setTextSize(20);
            errorView.setText("The content view in PtrFrameLayout is empty. Do you forget to specify its id in xml layout file?");
            mContentView = errorView;
            addView(mContentView);
        }
        if (mHeaderView != null) {
            mHeaderView.bringToFront();
        }
        super.onFinishInflate();
    }

    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p != null && p instanceof LayoutParams;
    }

    @Override
    protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    }

    @Override
    protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParams(p);
    }

    @Override
    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }

    public void setHandler(PullToRefreshHandler handler) {
        mHandler = handler;
    }

    class ScrollChecker implements Runnable {

        private int mLastFlingY;
        private Scroller mScroller;
        private boolean mIsRunning = false;
        private int mStart;
        private int mTo;

        public ScrollChecker() {
            mScroller = new Scroller(getContext());
        }

        public void run() {
            boolean finish = !mScroller.computeScrollOffset() || mScroller.isFinished();
            int curY = mScroller.getCurrY();
            int deltaY = curY - mLastFlingY;
            if (!finish) {
                mLastFlingY = curY;
                movePos(deltaY);
                post(this);
            } else {
                finish();
            }
        }

        private void finish() {
            reset();
            onScrollFinish();
        }

        private void reset() {
            mIsRunning = false;
            mLastFlingY = 0;
            removeCallbacks(this);
        }

        private void destroy() {
            reset();
            if (!mScroller.isFinished()) {
                mScroller.forceFinished(true);
            }
        }

        public void abortIfWorking() {
            if (mIsRunning) {
                if (!mScroller.isFinished()) {
                    mScroller.forceFinished(true);
                }
                onScrollAbort();
                reset();
            }
        }

        public void tryToScrollTo(int to, int duration) {
            if (mIndicator.isAlreadyHere(to)) {
                return;
            }
            mStart = mIndicator.getCurrentPos();
            mTo = to;
            int distance = to - mStart;
            removeCallbacks(this);

            mLastFlingY = 0;

            if (!mScroller.isFinished()) {
                mScroller.forceFinished(true);
            }
            mScroller.startScroll(0, 0, 0, distance, duration);
            post(this);
            mIsRunning = true;
        }
    }

    public static class LayoutParams extends MarginLayoutParams {

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }
    }
}
